package com.ysd.lis.util.compare;


import com.ysd.lis.entity.lab.LabMaininfo;
import lombok.*;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;

/**
 * 使用须知：
 * （1）该工具类主要用于两个同类对象的属性值比较；
 * （2）使用本工具类前，请将对应的类属性上打上 @Compare("xxx") 注解，其中xxx为字段的表达名称；
 * （3）为了比较灵活，只有打了该注解才会进行比较，不打的字段则不会进行比较
 * （4）比较后，只会返回有变化的字段，无变化的字符则不返回
 *
 * @author LuoXS
 * @date 2024/5/23
 * version 1.0
 */
public class CompareUtils<T> {

    private static final String COMMA = "，";

    public static Map<String, Object[]> compareFields(Object oldMaininfo, Object newMaininfo) {
        Map<String, Object[]> differences = new HashMap<>();
        Class<?> aClass = oldMaininfo.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value1 = field.get(oldMaininfo);
                Object value2 = field.get(newMaininfo);
                if (!Objects.equals(value1, value2)) {
                    differences.put(field.getName(), new Object[]{value1, value2});
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return differences;
    }


    /**
     * 属性比较
     *
     * @param source 源数据对象
     * @param target 目标数据对象
     * @return 对应属性值的比较变化
     */
    public String compare(T source, T target) {
        return compare(source, target, null);
    }


    /**
     * 属性比较
     *
     * @param source              源数据对象
     * @param target              目标数据对象
     * @param ignoreCompareFields 忽略比较的字段
     * @return 对应属性值的比较变化
     */
    public String compare(T source, T target, List<String> ignoreCompareFields) {
        if (Objects.isNull(source) && Objects.isNull(target)) {
            return "";
        }
        Map<String, CompareNode> sourceMap = this.getFiledValueMap(source);
        Map<String, CompareNode> targetMap = this.getFiledValueMap(target);
        if (sourceMap.isEmpty() && targetMap.isEmpty()) {
            return "";
        }
        // 如果源数据为空，则只显示目标数据，不显示属性变化情况
        if (sourceMap.isEmpty()) {
            return doEmpty(targetMap, ignoreCompareFields);
        }
        // 如果源数据为空，则显示属性变化情况
        String s = doCompare(sourceMap, targetMap, ignoreCompareFields);
        if (!s.endsWith(COMMA)) {
            return s;
        }
        return s.substring(0, s.length() - 1);
    }

    private String doEmpty(Map<String, CompareNode> targetMap, List<String> ignoreCompareFields) {
        StringBuilder sb = new StringBuilder();
        Collection<CompareNode> values = targetMap.values();
        int size = values.size();
        int current = 0;
        for (CompareNode node : values) {
            current++;
            Object o = Optional.ofNullable(node.getFieldValue()).orElse("");
            if (Objects.nonNull(ignoreCompareFields) && ignoreCompareFields.contains(node.getFieldKey())) {
                continue;
            }
            if (!o.toString().isEmpty()) {
                sb.append("[").append(node.getFieldName()).append("：").append(o).append("]");
                if (current < size) {
                    sb.append(COMMA);
                }
            }
        }
        return sb.toString();
    }

    private String doCompare(Map<String, CompareNode> sourceMap, Map<String, CompareNode> targetMap, List<String> ignoreCompareFields) {
        StringBuilder sb = new StringBuilder();
        Set<String> keys = sourceMap.keySet();
        int size = keys.size();
        int current = 0;
        for (String key : keys) {
            current++;
            CompareNode sn = sourceMap.get(key);
            CompareNode tn = targetMap.get(key);
            if (Objects.nonNull(ignoreCompareFields) && ignoreCompareFields.contains(sn.getFieldKey())) {
                continue;
            }
            String sv = Optional.ofNullable(sn.getFieldValue()).orElse("").toString();
            String tv = Optional.ofNullable(tn.getFieldValue()).orElse("").toString();
            // 只有两者属性值不一致时, 才显示变化情况
            if (!sv.equals(tv)) {
                sb.append(String.format("[%s：%s -> %s]", sn.getFieldName(), sv, tv));
                if (current < size) {
                    sb.append(COMMA);
                }
            }
        }
        return sb.toString();
    }

    private Map<String, CompareNode> getFiledValueMap(T t) {
        if (Objects.isNull(t)) {
            return Collections.emptyMap();
        }
        Field[] fields = t.getClass().getDeclaredFields();
        if (fields.length == 0) {
            return Collections.emptyMap();
        }
        Map<String, CompareNode> map = new LinkedHashMap<>();
        for (Field field : fields) {
            Compare compareAnnotation = field.getAnnotation(Compare.class);
            if (Objects.isNull(compareAnnotation)) {
                continue;
            }
            field.setAccessible(true);
            try {
                String fieldKey = field.getName();
                CompareNode node = new CompareNode();
                node.setFieldKey(fieldKey);
                node.setFieldValue(field.get(t));
                node.setFieldName(compareAnnotation.value());
                map.put(field.getName(), node);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    public static <T> List<ModifiedPropertyInfo> getDifferentProperty(T source, T target, String... ignoreProperties) {
        if (source != null && target != null) {
            if (ignoreProperties == null || ignoreProperties.length > 0) {
                if (source.equals(target)) {
                    return Collections.emptyList();
                }
            }
            List<PropertyModelInfo> oldObjectPropertyValue = getObjectPropertyValue(source, ignoreProperties);
            if (!CollectionUtils.isEmpty(oldObjectPropertyValue)) {
                List<ModifiedPropertyInfo> modifiedPropertyInfos = new ArrayList<>(oldObjectPropertyValue.size());

                List<PropertyModelInfo> newObjectPropertyValue = getObjectPropertyValue(target, ignoreProperties);
                Map<String, Object> objectMap = new HashMap<>(newObjectPropertyValue.size());
                for (PropertyModelInfo propertyModelInfo : newObjectPropertyValue) {
                    String propertyName = propertyModelInfo.getPropertyName();
                    Object value = propertyModelInfo.getValue();
                    objectMap.put(propertyName, value);
                }

                for (PropertyModelInfo propertyModelInfo : oldObjectPropertyValue) {
                    String propertyName = propertyModelInfo.getPropertyName();
                    Object value = propertyModelInfo.getValue();
                    if (objectMap.containsKey(propertyName)) {
                        Object newValue = objectMap.get(propertyName);
                        ModifiedPropertyInfo modifiedPropertyInfo = new ModifiedPropertyInfo();
                        if (value != null && newValue != null) {
                            boolean equal = false;
                            if (value instanceof BigDecimal) {
                                // BigDecimal 用 equal  比较
                                if (((BigDecimal) value).compareTo((BigDecimal) newValue) != 0) {
                                    equal = true;
                                }
                            } else if (!value.equals(newValue)) {
                                equal = true;
                            }
                            if (equal) {
                                modifiedPropertyInfo.setPropertyName(propertyName);
                                modifiedPropertyInfo.setOldValue(value);
                                modifiedPropertyInfo.setNewValue(newValue);
                                modifiedPropertyInfos.add(modifiedPropertyInfo);
                            }
                        } else if (value != null || newValue != null) {
                            modifiedPropertyInfo.setPropertyName(propertyName);
                            modifiedPropertyInfo.setOldValue(value);
                            modifiedPropertyInfo.setNewValue(newValue);
                            modifiedPropertyInfos.add(modifiedPropertyInfo);
                        }
                    }
                }
                return modifiedPropertyInfos;
            }
        }
        return Collections.emptyList();
    }

    public static <T> List<PropertyModelInfo> getObjectPropertyValue(T obj, String... ignoreProperties) {
        if (obj != null) {

            Class<?> objClass = obj.getClass();
            PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(objClass);
            List<PropertyModelInfo> modelInfos = new ArrayList<>(propertyDescriptors.length);

            List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                String name = propertyDescriptor.getName();
                if (readMethod != null && (ignoreList == null || !ignoreList.contains(name))) {
                    Object invoke = null;
                    Class<?> returnType = readMethod.getReturnType();
                    try {
                        invoke = readMethod.invoke(obj);
                        PropertyModelInfo propertyModelInfo = new PropertyModelInfo();
                        propertyModelInfo.setPropertyName(name);
                        propertyModelInfo.setValue(invoke);
                        propertyModelInfo.setReturnType(returnType);
                        modelInfos.add(propertyModelInfo);
                    } catch (IllegalAccessException | InvocationTargetException e) {

                    }
                }
            }
            return modelInfos;
        }
        return Collections.emptyList();
    }


    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class PropertyModelInfo {
        //属性名
        private String propertyName;
        // 属性值
        private Object value;
        // 返回值类型
        private Class<?> returnType;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class ModifiedPropertyInfo implements Serializable {

        // 对应的属性名
        private String propertyName;

        // 未修改之前的值
        private Object oldValue;

        // 修改后的值
        private Object newValue;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class User {
        @Compare("名字")
        String name;
    }


    public static void main(String[] args) {

        User aa = User.builder()
                .name("aa")
                .build();
        User bb = User.builder()
                .name("bb")
                .build();
        String compare = new CompareUtils<User>().compare(null, bb);
        System.out.println(compare);
    }
}